PHP 中,Closure 又稱為 Anonymous Function,中文譯為匿名函式:從字面上的意思上推敲,就是「沒有名字的函式」。
$inc = function (int $n): int {
return $n + 1;
}
$inc(10); // 11
通常 Closure 用於「具有變化可能性」的函式參數,舉例來說在各 Routing 的實作中,時常可以看到以下用法
<?php
Route::get('/', function () { return 'index'; });
Route::get('/{name}', function ($name) { return "Hello, $name"; };
此處借鑑了 Laravel 的 Routing 寫法:第一個參數為 Path,第二個參數為這個 Path 的處理方法。
如果要將外部變數傳給 Closure,需要以 use 明確指定,且 Closure 中的變更不會影響外部變數:
$number = 10;
$c = function () use ($number) {
return ++$n;
};
$c(); // 11
$number; // 10
如果希望 Closure 中的變更影響外部變數,可以利用 & 將匿名函式中的變更影響原本的外部變數
$number = 10;
$c = function () use (&$number) {
return ++$n;
};
$c(); // 11
$number; // 11
PHP 在處理 Closure 時,是產生一個 Closure Class,這個 Class 有一些特質
__consturct 為私有,故此 Class 無法被 new 出來bind 及 bindTo 兩個 methods,兩個功能類似但 bind 是 static__invoke method,但僅是為了與 Callable Object 保持一致性,與 Closure 的執行無關bind 與 bindTobindTo 用於複製一個 Closure,並且使用原本的 $this 及其可見性。
假設存在 Class A:
<?php
class A
{
protected $val;
public function __construct($val) {
$this->val = $val;
}
public function getClosure(): Closure {
return function () {
return $this->val;
};
}
}
我們可以用以下方式利用 getClosure()
<?php
$firstClass = new A(10);
$secondClass = new A(20);
$aClosure = $firstClass->getClosure();
$aClosure(); // 10
$bClosure = $aClosure->bindTo($secondClass);
$bClosure(); // 20
因為 $aClosure 是從 $firstClass 中出來的,所以回傳 10;因為 $bClosure 是將 $aClosure 這個 Closure 重新綁定到 $secondClass,所以會回傳 20。
順帶一提,bindTo 可以改變 method 的可見性,舉例來說
<?php
class A
{
protected $val = 10;
public function getClosure() {
return function () {
return $this->val;
};
}
}
class B
{
protected $val = 20;
}
$a = new A();
$b = new B();
$aClosure = $a->getClosure();
$aClosure(); // 10
// 在這邊將 $bClosure 的可見性設為 B Class
// 如此一來,$bClosure 就可以存取 B 的 protected variable
$bClosure = $aClosure->bindTo($b, B::class);
$bClosure(); // 20
bind 為 bindTo 的靜態版本,其用法基本上與 bindTo 類似,差別僅在於 Closure 可以在 bind 的第一個參數中指定
class A
{
protected $val = 10;
}
Closure::bind(function () {
return $this->val;
}, new A(), A::class);
bind 及 bindTo 不應是用來「複製」 Closure 使用,如果單純只是要複製一個 Closure 的話應該使用 clone
$c = function (int $n): int { return $n + 1; }
$cCopy = clone $c;
在 PHP 7.4 中,Arrow Functino 將簡化 Closure 的寫法。
$c = fn($x) => ++$x;
// $c = function ($x) { return $++x; }
另外,Arrow Function 會自動捕獲外部變數,所以不再需要使用 use
$y = 10;
$c = fn($x) => $x + $y;
$c(100); // 110
今天的內容針對 Closure 做了比較詳細的解釋。
事實上,在部份的 Framework 中已經大量使用 Closure 實現類似 Functional Programming 的 Lambda,雖然在目前(PHP 7.3)中還是有所不便,但這樣的狀況在 Arrow Function 出現後應該會有所改善。